﻿using SQLMap.Core.Strategy;
using System;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
/*****************************************************************************
 * 
 * 说 明：CommandParser类
 * 用 途：把SQLMAP映射所得的Command对象，进行解析处理，并利用IDbConnection对象的派生对象的CreateCommand方法创建IDbCommand对象
 * 这样把对SQLMAP的解析处理，从Command对象中抽离，形成一个命令解析器，开发者可以通过实现ICommandParser接口，来实现自己的解析器。
 * Command与CommandParser之间通过ICommandParser接口进行解耦。
 * 
 * 
 * ***************************************************************************/
namespace SQLMap.Core.Support
{
    /// <summary>
    /// <![CDATA[SqlMap-CommandParser]]>
    /// </summary>
    public class CommandParser : ICommandParser
    {

        /// <summary>
        /// <![CDATA[生成IDbCommand对象]]>
        /// </summary>
        /// <param name="command"><![CDATA[sqlmap中command对象]]></param>
        /// <param name="connection"><![CDATA[IDbConnection]]></param>
        /// <param name="parameters"><![CDATA[可选参数]]></param>
        /// <returns></returns>
        public virtual IDbCommand Build(Command command, IDbConnection connection, object parameters = null)
        {
            IDbCommand dbCommand = connection.CreateCommand();//创建dbCommand对象

            #region 1、处理条件
            StringBuilder stbSql = new StringBuilder();
            if (null != command.Condition)
            {
                Type type = parameters.GetType();
                //条件
                foreach (var item in command.Condition.Filters)
                {
                    //变量
                    if (item.Value.IndexOf("@") > -1)
                    {
                        //如果属性不存在，则忽略该条件
                        var property = type.GetProperty(item.Value.TrimStart('@'));
                        if (null == property ||
                            property.GetValue(parameters, null) == null) continue;
                    }
                    stbSql.Append(" ");
                    stbSql.Append(item.Connector);
                    stbSql.Append(" ");
                    stbSql.AppendFormat("{0} {1} {2}", item.Field, item.Operator, item.Value);
                }

                //分组条件
                if (command.Condition.GroupFilter != null &&
                    command.Condition.GroupFilter.Count > 0)
                {
                    int i = 0;
                    foreach (var item in command.Condition.GroupFilter)
                    {
                        //变量
                        if (item.Value.IndexOf("@") > -1)
                        {
                            //如果属性不存在，则忽略该条件
                            var property = type.GetProperty(item.Value.TrimStart('@'));
                            if (null == property ||
                                property.GetValue(parameters, null) == null)
                            {
                                ++i;
                                continue;
                            }
                        }
                        if (i == 0)
                        {
                            stbSql.Append(" And (");
                        }
                        else
                        {
                            stbSql.Append(" ");
                            stbSql.Append(item.Connector);
                            stbSql.Append(" ");
                        }
                        ++i;
                        stbSql.AppendFormat("{0} {1} {2}", item.Field, item.Operator, item.Value);
                        if (i == command.Condition.GroupFilter.Count - 1) stbSql.Append(")");
                    }
                }

                if (string.IsNullOrEmpty(command.OrderBy) == false)
                {
                    stbSql.Append(" ");
                    stbSql.Append(command.OrderBy);
                }

                if (string.IsNullOrEmpty(command.GroupBy) == false)
                {
                    stbSql.Append(" ");
                    stbSql.Append(command.GroupBy);
                }
            }
            string strCommandText = command.CommandText + stbSql.ToString();//注意：切莫对command对象做任何更改，多线程并发问题
            #endregion 1、处理条件

            #region 2、处理参数
            if (parameters != null)
            {
                //2.1、利用正则提取参数
                var matches = System.Text.RegularExpressions.Regex.Matches(command.CommandText, "@(?<name>[a-zA-Z_]+)", RegexOptions.Singleline | RegexOptions.Multiline);
                Type parameterType = parameters.GetType();//参数对象类型

                foreach (Match item in matches)
                {
                    string strParameterName = item.Groups["name"].Value.Trim();//参数名
                    if (dbCommand.Parameters.Contains(strParameterName)) continue;//如果参数存在，则跳过
                    PropertyInfo propertyInfo = parameterType.GetProperty(strParameterName);//属性
                    if (null == propertyInfo) continue;

                    //参数
                    IDataParameter parameter = new SqlParameter();
                    parameter.ParameterName = propertyInfo.Name;
                    parameter.Value = propertyInfo.GetValue(parameters, null);
                    dbCommand.Parameters.Add(parameter);
                }

                //2.2、循环处理SQLMAP中配置参数
                foreach (var parameter in command.Parameters)
                {
                    string strParameterName = parameter.Name.TrimStart('@');
                    PropertyInfo propertyInfo = parameterType.GetProperty(strParameterName);

                    //参数
                    SqlParameter sqlParameter = null;
                    if (dbCommand.Parameters.Contains(strParameterName))
                    {
                        sqlParameter = (SqlParameter)dbCommand.Parameters[strParameterName];
                    }
                    else
                    {
                        sqlParameter = new SqlParameter();
                        sqlParameter.ParameterName = strParameterName;
                        sqlParameter.Value = propertyInfo.GetValue(parameters, null) ?? DBNull.Value;
                        dbCommand.Parameters.Add(sqlParameter);
                    }
                    sqlParameter.SqlDbType = parameter.DbType;
                    sqlParameter.Size = parameter.Size;
                    sqlParameter.Direction = parameter.Direction;
                }
            }
            #endregion 2、处理参数

            #region 3、Command设置
            dbCommand.CommandType = command.CommandType;
            dbCommand.CommandText = strCommandText;
            #endregion 3、command设置

            return dbCommand;
        }
    }
}
